package com.jzoom.caster;

import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.citywithincity.utils.JsonUtil;


/**
 *
 * This tool box can cast a value into another class. Caster.to( value ,
 * Integer.class)
 * 
 * 
 * 目前支持基本数据
 * 
 *
 * @author Administrator
 */
public class Caster {

	public static class CasterException extends RuntimeException {

		public CasterException(String message) {
			super(message);
		}
		
		public CasterException(Exception e) {
			super(e);
		}

		/**
		 * 
		 */
		private static final long serialVersionUID = -4063979897278480246L;

	}

	private static Map<String, ValueCaster> map;

	static {
		map = new ConcurrentHashMap<String, ValueCaster>();

		register(Object.class, String.class, new Object2String());

		//number to other
		Caster.register(Number.class, Double.class, new Number2Double());
		Caster.register(Number.class, Integer.class, new Number2Integer());
		Caster.register(Number.class, Boolean.class, new Number2Boolean());
		Caster.register(Number.class, Float.class, new Number2Float());
		Caster.register(Number.class, Short.class, new Number2Short());
		Caster.register(Number.class, Character.class, new Number2Char());
		Caster.register(Number.class, Byte.class, new Number2Byte());
		Caster.register(Number.class, Long.class, new Number2Long());

		//string to other
		Caster.register(String.class, Double.class, new String2Double());
		Caster.register(String.class, Integer.class, new String2Integer());
		Caster.register(String.class, Boolean.class, new String2Boolean());
		Caster.register(String.class, Float.class, new String2Float());
		Caster.register(String.class, Short.class, new String2Short());
		Caster.register(String.class, Character.class, new String2Char());
		Caster.register(String.class, Byte.class, new String2Byte());
		Caster.register(String.class, Long.class, new String2Long());
		
		Caster.register(String.class, int.class, new String2Integer());
		Caster.register(String.class, boolean.class, new String2Boolean());
		Caster.register(String.class, long.class, new String2Long());
		
		//date
		Caster.register(java.util.Date.class, java.sql.Date.class, new Date2SqlDate());
		
		//CLASS
		Caster.register(String.class, Class.class, new String2Class());
		
		//json
		Caster.register(String.class, Map.class, new String2Map());
		Caster.register(String.class, List.class, new String2List());
		Caster.register(List.class, String.class, new List2String());
		Caster.register(Map.class, String.class, new Map2String());
		
	}
	
	private static class String2Map implements ValueCaster {

		@Override
		public Object to(Object src) {
			try {
				return JsonUtil.read( (String)src );
			} catch (IOException e) {
				throw new CasterException(e);
			}
		}

	}
	
	private static class String2List implements ValueCaster {

		@Override
		public Object to(Object src) {
			try {
				return JsonUtil.readList( (String)src );
			} catch (IOException e) {
				throw new CasterException(e);
			}
		}
	}
	
	private static class List2String implements ValueCaster {

		@Override
		public Object to(Object src) {
			return JsonUtil.toJson(src);
		}
	}
	
	
	private static class Map2String implements ValueCaster {

		@Override
		public Object to(Object src) {
			return JsonUtil.toJson(src);
		}
	}
	
	private static class String2Class implements ValueCaster {

		@Override
		public Object to(Object src) {
			try {
				return Class.forName((String)src);
			} catch (ClassNotFoundException e) {
				throw new CasterException(e);
			}
		}

	}
	
	
	
	private static class Date2SqlDate implements ValueCaster {

		@Override
		public Object to(Object src) {
			java.util.Date date = (java.util.Date)src;
			return new java.sql.Date(date.getTime());
		}

	}
	
	
	
	private static class String2Byte implements ValueCaster {

		@Override
		public Object to(Object src) {
			return Byte.parseByte((String)src);
		}

	}
	
	private static class String2Char implements ValueCaster {

		@Override
		public Object to(Object src) {
			return ((String)src).charAt(0);
		}

	}
	private static class String2Short implements ValueCaster {

		@Override
		public Object to(Object src) {
			return Short.parseShort((String) src);
		}

	}

	
	private static class String2Float implements ValueCaster {

		@Override
		public Object to(Object src) {
			return Float.parseFloat((String) src);
		}

	}

	private static class String2Boolean implements ValueCaster {

		@Override
		public Object to(Object src) {
			return Boolean.parseBoolean((String) src);
		}

	}
	
	

	
	private static class String2Integer implements ValueCaster {

		@Override
		public Object to(Object src) {
			String str =  ((String) src).trim();
			if (str.contains(".")) {
				return (int) (double) Double.parseDouble(str);
			}
			if(str.length()==0) {
				return 0;
			}
			return Integer.parseInt(str);
		}

	}

	private static class String2Long implements ValueCaster {

		@Override
		public Object to(Object src) {
			String str =  ((String) src).trim();
			if (str.contains(".")) {
				return (long) (double) Double.parseDouble(str);
			}
			if(str.length()==0) {
				return 0;
			}
			return Long.parseLong(str );
		}

	}
	private static class String2Double implements ValueCaster {

		@Override
		public Object to(Object src) {
			return Double.parseDouble((String) src);
		}

	}
	private static class Number2Long implements ValueCaster {

		@Override
		public Object to(Object src) {
			return ((Number) src).longValue();
		}
	}
	private static class Number2Byte implements ValueCaster {

		@Override
		public Object to(Object src) {
			return ((Number) src).byteValue();
		}
	}
	
	private static class Number2Char implements ValueCaster {

		@Override
		public Object to(Object src) {
			return (char) ((Number) src).shortValue();
		}

	}
	
	private static class Number2Integer implements ValueCaster {

		@Override
		public Object to(Object src) {
			return ((Number) src).intValue();
		}

	}
	
	private static class Number2Short implements ValueCaster {

		@Override
		public Object to(Object src) {
			return ((Number) src).shortValue();
		}

	}

	
	private static class Number2Float implements ValueCaster {

		@Override
		public Object to(Object src) {
			return ((Number) src).floatValue();
		}

	}

	private static class Number2Boolean implements ValueCaster {

		@Override
		public Object to(Object src) {
			return ((Number) src).intValue() == 0 ? Boolean.FALSE : Boolean.TRUE;
		}

	}

	private static class Number2Double implements ValueCaster {

		@Override
		public Object to(Object src) {
			return ((Number) src).doubleValue();
		}

	}

	/**
	 *
	 * T cast some class to another class, you must register the tow classes and
	 * a caster .
	 *
	 *
	 * @param src
	 * @param to
	 * @param caster
	 */
	public static void register(Class<?> src, Class<?> to, ValueCaster caster) {
		map.put(getKey(src, to), caster);
	}

	/**
	 * Get convrt src and target key in map
	 * 
	 * @param src
	 * @param to
	 * @return
	 */
	private static String getKey(Class<?> src, Class<?> to) {
		return new StringBuilder().append(src.getName()).append("2").append(to.getName()).toString();
	}

	/**
	 * Extra class to array. Including self, super class, interfaces
	 * 
	 * @param src
	 * @return
	 */
	private static String[] extraClass(Class<?> src) {
		Set<Class<?>> set = new LinkedHashSet<Class<?>>();

		extraClass(set, src);

		String[] result = new String[set.size()];
		int i = 0;
		for (Class<?> type : set) {
			result[i++] = type.getName();
		}
		return result;
	}

	private static void extraClass(Set<Class<?>> set, Class<?> src) {
		if (src == null) {
			return;
		}
		if (src.isInterface()) {
			extraInterface(set, src);
		} else {
			if (src == Object.class) {
				set.add(Object.class);
				return;
			}

			Class<?>[] interfaces = src.getInterfaces();
			for (Class<?> inter : interfaces) {
				extraInterface(set, inter);
			}

			extraClass(set, src.getSuperclass());
			set.add(src);
		}

	}

	private static void extraInterface(Set<Class<?>> set, Class<?> src) {
		set.add(src);
		src = src.getSuperclass();
		if (src == null)
			return;
		extraInterface(set, src);
	}
	private static Class<?> getWrapClass(Class<?> type) {
		if (type == int.class) {
			return Integer.class;
		}
		if (type == boolean.class) {
			return Boolean.class;
		}
		if (type == double.class) {
			return Double.class;
		}
		if (type == float.class) {
			return Float.class;
		}
		if (type == long.class) {
			return Long.class;
		}
		if (type == short.class) {
			return Short.class;
		}
		if (type == byte.class) {
			return Byte.class;
		}
		if (type == char.class) {
			return Character.class;
		}
		throw new RuntimeException("impossible");
	}
	private static Object getDefaultValue(Class<?> type) {
		if (type == int.class) {
			return 0;
		}
		if (type == boolean.class) {
			return false;
		}
		if (type == double.class) {
			return (double) 0;
		}
		if (type == float.class) {
			return (float) 0;
		}
		if (type == long.class) {
			return 0L;
		}
		if (type == short.class) {
			return (short) 0;
		}
		if (type == byte.class) {
			return (byte) 0;
		}
		if (type == char.class) {
			return (char) 0;
		}
		throw new RuntimeException("impossible");
	}

	@SuppressWarnings("unchecked")
	public static <T> T to(Object src, Class<?> type) {
		if (src == null) {
			if (type.isPrimitive()) {
				// default of primitive value
				return (T) getDefaultValue(type);
			}
			return null;
		}
		
		if(type==null) {
			
			//这里没有什么好办法，只能先返回null再说
			return null;
		}

		Class<?> srcType = src.getClass();
		if (srcType == type || type.isAssignableFrom(srcType)) {
			return (T) src;
		}
		String key = getKey(srcType, type);
		ValueCaster caster = map.get(key);
		if (caster == null) {
			caster = get(srcType, type);
			map.put(key, caster);
		}

		return (T) caster.to(src);
	}

	static final class WrapValueCaster implements ValueCaster {
		private final ValueCaster caster;

		WrapValueCaster(ValueCaster caster) {
			this.caster = caster;
		}

		@Override
		public final Object to(Object src) {
			if (src == null)
				return null;
			return caster.to(src);
		}
	}

	static final class WrapPriValueCaster implements ValueCaster {
		private final ValueCaster caster;
		private final Class<?> type;

		WrapPriValueCaster(ValueCaster caster, Class<?> toType) {
			this.caster = caster;
			this.type = toType;
		}

		@Override
		public final Object to(Object src) {
			if (src == null) {
				return getDefaultValue(type);
			}
			return caster.to(src);
		}
	}

	/**
	 * Get a wrapped ValueCaster that check null value and return default value
	 * when value is null.
	 * 
	 * @param srcType
	 * @param toType
	 * @return
	 */
	public static ValueCaster wrap(Class<?> srcType, Class<?> toType) {
		if (srcType == null || toType == null) {
			throw new NullPointerException("srcType and toType must not be null");
		}
		if (toType.isPrimitive()) {
			//转化的类型指定类的包装类
			return new WrapPriValueCaster(get(srcType, getWrapClass(toType)  ), toType);
		}

		return new WrapValueCaster(get(srcType, toType));
	}

	private static class EqValueCaster implements ValueCaster {

		@Override
		public Object to(Object src) {
			return src;
		}

	}

	/**
	 * dest 是否 是 src的包装类
	 *
	 *
	 * isWrapClass(int.class,Integer.class)==true
	 * isWrapClass(Integer.class,int.class)==false
	 *
	 *
	 * @param src
	 *            如 int.class
	 * @param dest
	 *            如 Integer.class
	 * @return
	 */
	private static boolean isWrapClass(Class<?> src, Class<?> dest) {
		try {
			
			return ((Class<?>) dest.getField("TYPE").get(null)) == src;
		} catch (Exception e) {
			return false;
		}
	}

	private static EqValueCaster eqValueCaster = new EqValueCaster();

	private static ValueCaster get(Class<?> srcType, Class<?> toType) {
		/**
		 * 如果是int 转 Integer 或者是Integer 转 int,则可以相互转化
		 */
		if (isWrapClass(srcType, toType) || isWrapClass(toType, srcType)) {
			return eqValueCaster;
		}

		/**
		 * 否则需要将class全部解出来，以便加以判断
		 *
		 * 解出来是指：
		 *
		 * 超类（递归） 接口 、接口超类（递归）
		 *
		 */
		String[] srcs = extraClass(srcType);
		String[] tos = extraClass(toType);

		for (String s : srcs) {
			for (String t : tos) {
				String key = new StringBuilder(s).append("2").append(t).toString();
				ValueCaster caster = map.get(key);
				if (caster == null) {
					continue;
				}
				return caster;
			}
		}

		throw new CasterException(String.format("Cannot cast %s to %s ", srcType.getName(), toType.getName()));
	}

	private static class Object2String implements ValueCaster {

		@Override
		public Object to(Object src) {
			return String.valueOf(src);
		}

	}

}
